import { BlockchainConfigType, BlockchainNodeConfigType } from "../config.type";
import path from "path";
import fs from "fs";
import { NodeService } from "./NodeService";
import TOML from "@iarna/toml";
import { BridgeService } from "../bridge/BridgeService";
import { Log, LogStatus, LogType } from "../../util/Logger";
import { XrpSafeContractAddress } from "../bridge/contract/SafeContract";
import { DefaultGenesis } from "./DefaultGenesis";
import { exrpd, formatAmount } from "../util/exrpd";
import { execSync } from "child_process";

const Defaults = {
    evmDenom: "axrp",
    poaDenom: "apoa",
    validatorPoaAmount: 1_000_000_000,
    accountEvmAmount: 10,
    evmSupply: 10_000_000_000,
};

export class BlockchainService {
    constructor(private exportPath: string, private config: BlockchainConfigType, private bridgeService: BridgeService) {}

    get dockerImage(): string {
        return "peersyst/xrp-evm-blockchain:latest";
    }

    get chainId(): string {
        return `exrp_${this.config.chainId}-0`;
    }

    nodePath(nodeName: string): string {
        return path.join(this.exportPath, nodeName);
    }

    initializeNodes() {
        const nodes: BlockchainNodeConfigType[] = [];
        if (!Array.isArray(this.config.nodes)) {
            for (let i = 0; i < this.config.nodes; i++) {
                nodes.push({
                    name: `node-${i}`,
                    host: `node-${i}`,
                });
            }
        } else {
            nodes.push(...this.config.nodes);
        }

        const nodesWithService: (BlockchainNodeConfigType & { service: NodeService })[] = [];
        for (const node of nodes) {
            nodesWithService.push({ ...node, service: new NodeService(this.config, node.name, path.join(this.nodePath(node.name))) });
        }

        for (const node of nodesWithService) {
            node.service.initNode();
        }

        return nodesWithService;
    }

    overrideGenesis(nodePath: string) {
        const genesis = DefaultGenesis(this.config, Defaults.evmDenom, Defaults.poaDenom);
        fs.writeFileSync(path.join(nodePath, `/config/genesis.json`), JSON.stringify(genesis));
    }

    addGenesisBridgeContracts(nodePath: string) {
        const genesisPath = path.join(nodePath, "/config/genesis.json");
        const genesis = JSON.parse(fs.readFileSync(genesisPath).toString());
        const genesisWithContracts = this.bridgeService.addBridgeContracts(genesis);
        fs.writeFileSync(genesisPath, JSON.stringify(genesisWithContracts));
    }

    collectGenesisTransactions(srcNodePath: string, destNodePath: string, destNodeService: NodeService) {
        execSync(`cp -r ${path.join(srcNodePath, "/config/gentx/*")} ${path.join(destNodePath, "/config/gentx/")}`);
        destNodeService.collectGenTxs();
    }

    copyGenesis(srcNodePath: string, destNodePath: string) {
        fs.copyFileSync(path.join(srcNodePath, "/config/genesis.json"), path.join(destNodePath, "/config/genesis.json"));
    }

    overrideSeeds(nodePath: string, seeds: string) {
        const configPath = path.join(nodePath, "/config/config.toml");
        const rawConfig = fs.readFileSync(configPath).toString();
        const config: any = TOML.parse(rawConfig);
        config.p2p.seeds = seeds;
        const newConfig = TOML.stringify(config);
        fs.writeFileSync(configPath, newConfig);
    }

    configure(): BlockchainNodeConfigType[] {
        Log(LogType.Network, LogStatus.ToDo, "Configuring EVM Sidechain network...");
        const nodes = this.initializeNodes();
        Log(LogType.Network, LogStatus.Working, "Nodes initialized");
        const defaultNodePath = this.nodePath("default");
        const defaultNodeService = new NodeService(this.config, "default", defaultNodePath);
        defaultNodeService.initNode();

        this.overrideGenesis(defaultNodePath);
        Log(LogType.Network, LogStatus.Working, `Applied default genesis`);

        let allocatedSupply = 0;
        for (const node of nodes) {
            const amount = `${formatAmount(Defaults.validatorPoaAmount, Defaults.poaDenom)},${formatAmount(
                Defaults.accountEvmAmount,
                Defaults.evmDenom,
            )}`;
            defaultNodeService.addGenesisAccount(node.service.info.address, amount);
            allocatedSupply += Defaults.accountEvmAmount;
            Log(LogType.Network, LogStatus.Working, `Added node account ${node.service.info.address} with ${amount}`);
        }

        for (const witness of this.bridgeService.config.witnesses) {
            const cosmosAddress = JSON.parse(exrpd(`keys parse ${witness.evmAddress.replace("0x", "")} --output json`)).formats[0];
            const amount = formatAmount(Defaults.accountEvmAmount, Defaults.evmDenom, 18);
            defaultNodeService.addGenesisAccount(cosmosAddress, amount);
            Log(LogType.Network, LogStatus.Working, `Added witness account ${witness.evmAddress} with ${amount}`);
            allocatedSupply += Defaults.accountEvmAmount;
        }

        for (const account of this.config.extraAccounts) {
            const cosmosAddress = JSON.parse(exrpd(`keys parse ${account.address.replace("0x", "")} --output json`)).formats[0];
            const amount = formatAmount(account.balance, Defaults.evmDenom, 18);
            defaultNodeService.addGenesisAccount(cosmosAddress, amount);
            Log(LogType.Network, LogStatus.Working, `Added extra account ${account.address} with ${amount}`);
            allocatedSupply += account.balance;
        }

        const safeAddress = defaultNodeService.parseHexAddressToBech32(XrpSafeContractAddress);
        const chestAmount = formatAmount(Defaults.evmSupply - allocatedSupply, Defaults.evmDenom, 18);
        defaultNodeService.addGenesisAccount(safeAddress, chestAmount);
        Log(LogType.Network, LogStatus.Working, `Added chest account ${safeAddress} with ${chestAmount}`);

        this.addGenesisBridgeContracts(defaultNodePath);

        Log(LogType.Network, LogStatus.Working, `Added genesis bridge contracts`);

        fs.mkdirSync(path.join(defaultNodePath, "/config/gentx/"));
        for (const node of nodes) {
            fs.copyFileSync(
                path.join(defaultNodePath, `/config/genesis.json`),
                path.join(this.nodePath(node.name), `/config/genesis.json`),
            );
            node.service.signGenesisStakeTransaction(formatAmount(Defaults.validatorPoaAmount, Defaults.poaDenom));
            this.collectGenesisTransactions(this.nodePath(node.name), defaultNodePath, defaultNodeService);
        }

        for (const node of nodes) {
            this.copyGenesis(defaultNodePath, this.nodePath(node.name));
        }
        Log(LogType.Network, LogStatus.Working, `Distributed genesis to all nodes`);

        const seeds = nodes.map((node) => `${node.service.info.id}@${node.host}:26656`).join(",");
        for (const node of nodes) {
            this.overrideSeeds(this.nodePath(node.name), seeds);
        }

        Log(LogType.Network, LogStatus.Working, `Configured persistent peers to all nodes`);

        fs.copyFileSync(path.join(defaultNodePath, "/config/genesis.json"), path.join(this.exportPath, "genesis.json"));
        fs.writeFileSync(path.join(this.exportPath, "nodes.json"), JSON.stringify(nodes));

        Log(LogType.Network, LogStatus.Done, "EVM Sidechain network configured");
        return nodes;
    }
}
